﻿using System;
using System.Configuration;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Web;
using System.IO;
using System.Data.SqlClient;

namespace IntraVision.Web
{
    public class IntraVisionWebException : Exception
    {
        public int StatusCode { get; set; }

        public IntraVisionWebException(int status)
        {
            StatusCode = status;
        }
    }

    public static class ContextExtension
    {
        public static void EndResponse(this HttpContext context, int statusCode)
        {
            //Задаем страницу, на которую переадресовать после логина
            context.Session["RedirectAfterLogin"] = context.Request.AppRelativeCurrentExecutionFilePath + context.Request.PathInfo;
            context.Response.StatusCode = statusCode;
            context.Response.End();
        }
    }

    public class IVPHandler : IHttpHandler, System.Web.SessionState.IRequiresSessionState
    {
        #region IHttpHandler Members
        public bool IsReusable
        {
            get { return true; }
        }

        public void ProcessRequest(HttpContext context)
        {
            ReadSettings(context);

            //We receive URL in format like this: ~/Controller/Action/Param1/Param2?other_params
            string url = context.Request.AppRelativeCurrentExecutionFilePath + context.Request.PathInfo;

            string[] urlparts = URLHelper.GetURLParts(url);

            IController controller = ControllerFactory.GetController(urlparts[0]);
            if (controller == null)
            {
                ShowErrorStaticHtml(context, 404);
                return;
            }

            object[] parameters = new object[urlparts.Length - 2];
            for (int i = 2; i < urlparts.Length; i++)
                parameters[i - 2] = urlparts[i];

            MethodInfo mi = ControllerFactory.GetAction(controller, urlparts[1].ToLower(), parameters.Length);
            if (mi == null)
            {
                ShowErrorStaticHtml(context, 404);
                return;
            }

            //Проверка прав
            string userKey = ConfigurationManager.AppSettings["userKey"] ?? "user";

            MemberInfo member = controller.GetType();
            //Если у метода есть атрибуты безопасности, возьмем их от метода. Иначе - от класса
            if (mi.GetCustomAttributes(typeof(Security.SecurityAttribute), true).Length > 0) member = mi;

            if (!CheckSecurity(member, context.Session[userKey] as IUser))
            {
                // пользователь незалогинен и пытается зайти в систему не по базовому URL
                if (context.Session[userKey] == null && urlparts[0] != "login.ivp")
                {
                    //В зависимости от типа запроса, либо
                    // сохранение адреса перенаправления в сессии
                    if (context.Request["HTTP_X_REQUESTED_WITH"] != "XMLHttpRequest")
                    {
                        if (Convert.ToString(context.Session["RedirectAfterLogin"], CultureInfo.InvariantCulture) == url)
                            context.Session["RedirectAfterLogin"] = null;
                        else
                            context.Session["RedirectAfterLogin"] = url;
                        context.Response.Redirect("login.ivp", true);
                    }
                    else //либо просто 403 статус
                        ShowErrorStaticHtml(context, 403);
                    return;
                }
                else
                    ShowErrorStaticHtml(context, 403, url);
            }

            #region Localize
            string lang = Convert.ToString(context.Session["Language"]);
            //Пытаемся вытащить язык из cookie
            if (string.IsNullOrEmpty(lang))
            {
                string cookieKey = ConfigurationManager.AppSettings["languageCookieKey"];
                if (string.IsNullOrEmpty(cookieKey)) cookieKey = String.Format(CultureInfo.InvariantCulture, "LANG_{0}", context.Request.Url.Host);
                if (context.Request.Cookies[cookieKey] != null)
                    lang = context.Server.UrlDecode(context.Request.Cookies[cookieKey].Values["lang"]);
            }
            //Затем пытаемся получить его из веб-конфига
            if (string.IsNullOrEmpty(lang))
                lang = ConfigurationManager.AppSettings["DefaultLanguage"];
            //Если язык удалось определить, запишем его в сессию
            if (!string.IsNullOrEmpty(lang))
            {
                context.Session["Language"] = lang;
                Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(lang);
                Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(lang);
            }
            #endregion

            try
            {
                mi.Invoke(controller, parameters);
            }
            catch (Exception ex)
            {
                if (ex.GetType() == typeof(IntraVisionWebException))
                {
                    ShowErrorStaticHtml(context, ((IntraVisionWebException)ex).StatusCode);
                    return;
                }

                if (ex.InnerException != null && ex.InnerException.GetType() == typeof(IntraVisionWebException))
                {
                    ShowErrorStaticHtml(context, ((IntraVisionWebException)ex.InnerException).StatusCode);
                    return;
                }
                throw ex;
            }
        }
        #endregion

        //Читает общие настройки
        public void ReadSettings(HttpContext context)
        {
            //Чтение настроек из БД и запись их в объект Application
            if (context.Application["dbsettings"] == null && !String.IsNullOrEmpty(ConfigurationManager.AppSettings["settingsConnectionString"]))
            {
                var dbsettings = new Dictionary<string, string>();
                var connectionString = ConfigurationManager.ConnectionStrings[ConfigurationManager.AppSettings["settingsConnectionString"]].ConnectionString.Replace("{@appprefix}", context.Request.Url.Host.IndexOf('.') > 0 ? context.Request.Url.Host.Remove(context.Request.Url.Host.IndexOf('.'), context.Request.Url.Host.Length - context.Request.Url.Host.IndexOf('.')) : "");
                using (var connection = new SqlConnection(connectionString))
                {
                    try
                    {
                        connection.Open();
                        var cmd = new SqlCommand("SELECT [Key], [Value] FROM Settings", connection);
                        using (var reader = cmd.ExecuteReader())
                        {
                            if(reader != null)
                            {
                                while (reader.Read())
                                    dbsettings[reader["Key"].ToString()] = reader["Value"].ToString();
                            }
                        }
                    }
                    catch { }
                }
                context.Application.Add("dbsettings", dbsettings);
            }

            //Чтение настроек из web.config
            if (context.Application["configsettings"] == null && !String.IsNullOrEmpty(ConfigurationManager.AppSettings["settingsConnectionString"]))
            {
                var configsettings = new Dictionary<string, string>();
                if (ConfigurationManager.GetSection("settings") != null)
                {
                    var settings = ConfigurationManager.GetSection("settings") as NameValueCollection;
                    if (settings != null)
                    {
                        foreach (var key in settings.AllKeys)
                            configsettings[key] = settings[key];
                    }
                }
                context.Application.Add("configsettings", configsettings);
            }
        }

        private static bool CheckSecurity(MemberInfo member,IUser user)
        {
            var attrs = member.GetCustomAttributes(typeof(Security.SecurityAttribute), true);
            foreach (Security.SecurityAttribute attr in attrs)
            {
                if (!attr.IsOK(user)) return false;
            }
            return true;
        }

        // чтение статического HTML из файла вида ErrorCode.htm и вывод его на странице ошибки
        private static void ShowErrorStaticHtml(HttpContext context, int errorCode, string redirectAfterLoginUrl)
        {
            string errorHtmFile = HttpContext.Current.Server.MapPath(string.Format(CultureInfo.InvariantCulture, ("errors\\{0}.htm"), errorCode));
            string errorMessage = "";
            if (File.Exists(errorHtmFile))
            {
                var reader = new StreamReader(errorHtmFile, Encoding.GetEncoding(1251));
                errorMessage = reader.ReadToEnd();
                errorMessage = errorMessage.Replace("%MainPageUrl%", URLHelper.GetBaseHref(context));
                reader.Close();
            }
            ShowError(context, errorCode, errorMessage, redirectAfterLoginUrl);
        }

        private void ShowErrorStaticHtml(HttpContext context, int errorCode)
        {
            ShowErrorStaticHtml(context, errorCode, null);
        }

        // вывод страницы с кодом ошибки и сообщением об ошибке
        private static void ShowError(HttpContext context, int errorCode, string errorMessage, string redirectAfterLoginUrl)
        {
            // сохранение адреса перенаправнения в сессии, если пользователь не аутентифицирован
            if (context.Session[ConfigurationManager.AppSettings["userKey"] ?? "user"] == null && !string.IsNullOrEmpty(redirectAfterLoginUrl))
                context.Session["RedirectAfterLogin"] = redirectAfterLoginUrl;

            // запись информации в Response и отправка клиенту
            if (!string.IsNullOrEmpty(errorMessage))
                context.Response.Write(errorMessage);

            context.Response.StatusCode = errorCode;
            context.Response.End();
        }
    }
}
